﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.Objects;
using System.Reflection;
using System.Text;
using EntityFrameworkVisualizer.VisualizerObjectSources.CommandFormatters;

namespace EntityFrameworkVisualizer.VisualizerObjectSources
{
    /// <summary>
    /// Visualizer for <see cref="ObjectContext"/>.
    /// </summary>
    public class ObjectContextVisualizerObjectSource : QueryVisualizerObjectSource<ObjectContext>
    {
        /// <summary>
        /// Gets SQL source code.
        /// </summary>
        /// <param name="target">Target object.</param>
        /// <returns>SQL source code.</returns>
        protected override string GetSql(ObjectContext target)
        {
            var commands = GetContextCommands(target);
            return BuildString(commands);
        }

        /// <summary>
        /// Gets commands from context.
        /// </summary>
        /// <param name="context">Context.</param>
        /// <returns>List of commands.</returns>
        private static IEnumerable<DbCommand> GetContextCommands(ObjectContext context)
        {
            const string EntityAssemblyName = "System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";

            var entityAssemly = Assembly.Load(EntityAssemblyName);

            var updateTranslatorType = entityAssemly.GetType("System.Data.Mapping.Update.Internal.UpdateTranslator");

            var functionUpdateCommandType = entityAssemly.GetType("System.Data.Mapping.Update.Internal.FunctionUpdateCommand");

            var dynamicUpdateCommandType = entityAssemly.GetType("System.Data.Mapping.Update.Internal.DynamicUpdateCommand");

            var ctorParams = new object[]
                        {
                            context.ObjectStateManager,
                            ((EntityConnection)context.Connection).GetMetadataWorkspace(),
                            (EntityConnection)context.Connection,
                            context.CommandTimeout
                        };

            var updateTranslator = Activator.CreateInstance(
                updateTranslatorType,
                BindingFlags.NonPublic | BindingFlags.Instance, 
                null, 
                ctorParams, 
                null);

            MethodInfo produceCommandsMethod = updateTranslatorType
                .GetMethod("ProduceCommands", BindingFlags.Instance | BindingFlags.NonPublic);
            var updateCommands = produceCommandsMethod.Invoke(updateTranslator, null) as IEnumerable;

            foreach (object o in updateCommands)
            {
                if (functionUpdateCommandType.IsInstanceOfType(o))
                {
                    FieldInfo mdbCommandField = functionUpdateCommandType.GetField(
                        "m_dbCommand", BindingFlags.Instance | BindingFlags.NonPublic);

                    yield return mdbCommandField.GetValue(o) as DbCommand;
                }
                else if (dynamicUpdateCommandType.IsInstanceOfType(o))
                {
                    MethodInfo createCommandMethod = dynamicUpdateCommandType.GetMethod(
                        "CreateCommand", BindingFlags.Instance | BindingFlags.NonPublic);

                    var methodParams = new object[]
                    {
                        updateTranslator,
                        new Dictionary<int, object>()
                    };

                    yield return createCommandMethod.Invoke(o, methodParams) as DbCommand;
                }
            }
        }

        /// <summary>
        /// Formats command string.
        /// </summary>
        /// <param name="stringBuilder">Trace string.</param>
        /// <param name="command">Command.</param>
        private static void FormatCommand(StringBuilder stringBuilder, DbCommand command)
        {
            var formatter = DbCommandFormatterFactory.Create(command);

            if (command.CommandType == CommandType.StoredProcedure)
            {
                stringBuilder.AppendLine(formatter.FormatStoredProcedure());
            }
            else
            {
                stringBuilder.Append(formatter.DeclareParameters());
                stringBuilder.AppendLine();
                stringBuilder.AppendLine(command.CommandText);
            }
        }

        /// <summary>
        /// Builds string from list of commands.
        /// </summary>
        /// <param name="updateCommands">List of commands.</param>
        /// <returns>Trace string.</returns>
        private string BuildString(IEnumerable<DbCommand> updateCommands)
        {
            var separator = "go " + new string('-', 50);

            using (var enumerator = updateCommands.GetEnumerator())
            {
                var traceString = new StringBuilder();
                if (enumerator.MoveNext())
                {
                    FormatCommand(traceString, enumerator.Current);
                }

                while (enumerator.MoveNext())
                {
                    traceString.AppendLine();
                    traceString.AppendLine(separator);
                    traceString.AppendLine();
                    FormatCommand(traceString, enumerator.Current);
                }

                return traceString.ToString();
            }            
        }
    }
}
